﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using OpenTK;
using OpenTK.Graphics.OpenGL4;

namespace Common {

    [StructLayout(LayoutKind.Sequential)]
    struct PackedVertex : IComparable<PackedVertex>, IComparable {
        public Vector3 positon;
        public Vector2 uv;
        public Vector3 normal;
        [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern unsafe int memcmp(UIntPtr b1, UIntPtr b2, int count);

        private unsafe static int memcmp(void* b1, void* b2, int count) {
            return memcmp((UIntPtr)b1, (UIntPtr)b2, count);
        }
        public int CompareTo(object obj) {
            throw new NotImplementedException();
        }

        public int CompareTo(PackedVertex other) {
            throw new NotImplementedException();
        }

        public unsafe static bool operator <(PackedVertex a, PackedVertex b) {
            void* pA = (void*)&a;
            void* pB = (void*)&b;
            return memcmp(pA, pB, sizeof(PackedVertex)) > 0;
        }

        public unsafe static bool operator >(PackedVertex a, PackedVertex b) {
            void* pA = (void*)&a;
            void* pB = (void*)&b;
            return memcmp(pA, pB, sizeof(PackedVertex)) < 0;
        }

        public unsafe static bool operator ==(PackedVertex a, PackedVertex b) {
            void* pA = (void*)&a;
            void* pB = (void*)&b;
            return memcmp(pA, pB, sizeof(PackedVertex)) == 0;
        }
        public unsafe static bool operator !=(PackedVertex a, PackedVertex b) {
            void* pA = (void*)&a;
            void* pB = (void*)&b;
            return memcmp(pA, pB, sizeof(PackedVertex)) != 0;
        }

        public override int GetHashCode() {
            return positon.GetHashCode() ^ uv.GetHashCode() ^ normal.GetHashCode();
        }

        public override bool Equals(object obj) {
            return (PackedVertex)obj == this;
        }
    }
    public class VboIndexHelper {
        public static void IndexVbo(List<Vector3> inVertices, List<Vector2> inUvs, List<Vector3> inNormals, List<ushort> outIndices, List<Vector3> outVertices, List<Vector2> outUvs, List<Vector3> outNormals) {
            Dictionary<PackedVertex, ushort> VertexToOutIndex = new Dictionary<PackedVertex, ushort>();
            for (int i = 0; i < inVertices.Count; i++) {
                PackedVertex packed = new PackedVertex { positon = inVertices[i], uv = inUvs[i], normal = inNormals[i] };
                ushort index;
                bool found = getSimilarVertexIndex_fast(packed, VertexToOutIndex, out index);
                if (found) {
                    outIndices.Add(index);
                }
                else {
                    outVertices.Add(inVertices[i]);
                    outUvs.Add(inUvs[i]);
                    outNormals.Add(inNormals[i]);
                    ushort newIndex = (ushort)(outVertices.Count - 1);
                    outIndices.Add(newIndex);
                    VertexToOutIndex[packed] = newIndex;
                }
            }
        }

        private static bool getSimilarVertexIndex_fast(PackedVertex packed, Dictionary<PackedVertex, ushort> vertexToOutIndex, out ushort index) {
            if (vertexToOutIndex.ContainsKey(packed)) {
                index = vertexToOutIndex[packed];
                return true;
            }
            else {
                index = ushort.MaxValue;
                return false;
            }
        }
        private static bool getSimilarVertexIndex(
            Vector3 in_vertex, Vector2 in_uv, Vector3 in_normal,
            List<Vector3> out_vertices,
            List<Vector2> out_uvs,
            List<Vector3> out_normals,
            out ushort result) {
            for (int i = 0; i < out_vertices.Count; i++) {
                if (is_near(in_vertex.X, out_vertices[i].X) &&
                    is_near(in_vertex.Y, out_vertices[i].Y) &&
                    is_near(in_vertex.Z, out_vertices[i].Z) &&
                    is_near(in_uv.X, out_uvs[i].X) &&
                    is_near(in_uv.Y, out_uvs[i].Y) &&
                    is_near(in_normal.X, out_normals[i].X) &&
                    is_near(in_normal.Y, out_normals[i].Y) &&
                    is_near(in_normal.Z, out_normals[i].Z)

                    ) {
                    result = (ushort)i;
                    return true;
                }
            }
            result = ushort.MaxValue;
            return false;

        }

        static bool is_near(float v1, float v2) {
            return Math.Abs(v1 - v2) < 0.01f;
        }
        public static void indexVboTbn(
            List<Vector3> in_vertices,
            List<Vector2> in_uvs,
            List<Vector3> in_normals,
            List<Vector3> in_tangents,
            List<Vector3> in_bitangents,
            List<ushort> out_indices,
            List<Vector3> out_vertices,
            List<Vector2> out_uvs,
            List<Vector3> out_normals,
            List<Vector3> out_tangents,
            List<Vector3> out_bitangents
            ) {
            for (int i = 0; i < in_vertices.Count; i++) {
                ushort index;
                bool found = getSimilarVertexIndex(in_vertices[i], in_uvs[i], in_normals[i], out_vertices, out_uvs, out_normals, out index);
                if (found) {
                    out_indices.Add(index);
                    out_tangents[index] += in_tangents[i];
                    out_bitangents[index] += in_bitangents[i];
                }
                else {
                    out_vertices.Add(in_vertices[i]);
                    out_uvs.Add(in_uvs[i]);
                    out_normals.Add(in_normals[i]);
                    out_tangents.Add(in_tangents[i]);
                    out_bitangents.Add(in_bitangents[i]);
                    out_indices.Add((ushort)(out_vertices.Count - 1));
                }
            }

        }
    }

}

